/**
 * 
 */
package com.cea.papyrus.diagram.sequence.sedi.model.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.uml2.uml.Behavior;
import org.eclipse.uml2.uml.BehavioredClassifier;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.CombinedFragment;
import org.eclipse.uml2.uml.Comment;
import org.eclipse.uml2.uml.DestructionEvent;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ExecutionOccurrenceSpecification;
import org.eclipse.uml2.uml.Gate;
import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.InteractionConstraint;
import org.eclipse.uml2.uml.InteractionFragment;
import org.eclipse.uml2.uml.InteractionOperand;
import org.eclipse.uml2.uml.InteractionOperatorKind;
import org.eclipse.uml2.uml.InteractionUse;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Namespace;
import org.eclipse.uml2.uml.OccurrenceSpecification;
import org.eclipse.uml2.uml.OpaqueExpression;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.PackageableElement;
import org.eclipse.uml2.uml.StateInvariant;
import org.eclipse.uml2.uml.ValueSpecification;

import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * @author ymortier <yann.mortier@obeo.fr>
 */
public class ModelUtil {

    /**
     * Tests whether a given object is a View object references a model element of a
     * particular type.
     * 
     * @param o
     *            The instance to test.
     * @param c
     *            The desired model element class.
     * @returns <code>true</code> if
     *          <ul>
     *          <li><code>o</code> is not null,
     *          <li><code>o</code> is a view class and
     *          <li>the model element instance referenced by <code>o</code> is an
     *          instance of the class <code>c</code>.
     *          </ul>
     *          Returns <code>false</code> otherwise.
     * @throws NullPointerException
     *             if <code>c</code> is null.
     */
    @SuppressWarnings("unchecked")
    public static boolean neIsInstance(Object o, Class c) {
        if (o == null)
            return false;
        if (o instanceof GraphElement) {
            GraphElement ge = (GraphElement) o;
            if (ge.getSemanticModel() instanceof Uml1SemanticModelBridge) {
                return c.isInstance(((Uml1SemanticModelBridge) ge.getSemanticModel()).getElement());
            }
            return false;
        }
        return c.isInstance(o);
    }

    @SuppressWarnings("unchecked")
    public static <T> T getSemantic(GraphElement graphElement) {
        T semantic = null;
        if (graphElement != null
                && graphElement.getSemanticModel() instanceof Uml1SemanticModelBridge) {
            semantic = (T) ((Uml1SemanticModelBridge) graphElement.getSemanticModel()).getElement();
        }
        return semantic;
    }

    @SuppressWarnings("unchecked")
    public static List<Lifeline> getCovereds(InteractionFragment fragment) {
        return (List<Lifeline>) fragment.getCovereds();
    }

    public static Interaction getInteractionFor(InteractionFragment fragment) {
        if (fragment instanceof Interaction)
            return (Interaction) fragment;
        if (fragment.getEnclosingInteraction() != null)
            return fragment.getEnclosingInteraction();
        if (fragment.getOwner() instanceof InteractionFragment)
            return getInteractionFor((InteractionFragment) fragment.getOwner());

        return null; // TODO consider: throw InvArgException?
    }

    @SuppressWarnings("unchecked")
    public static List<InteractionFragment> getFragments(final InteractionOperand operand) {
        return (List<InteractionFragment>) operand.getFragments();
    }

    @SuppressWarnings("unchecked")
    public static List<InteractionFragment> getFragments(final Interaction interaction) {
        return (List<InteractionFragment>) interaction.getFragments();
    }

    @SuppressWarnings("unchecked")
    public static List<InteractionOperand> getOperands(final CombinedFragment cfragment) {
        return (List<InteractionOperand>) cfragment.getOperands();
    }

    @SuppressWarnings("unchecked")
    public static List<Gate> getGates(final Interaction interaction) {
        return (List<Gate>) interaction.getFormalGates();
    }

    @SuppressWarnings("unchecked")
    public static List<Gate> getGates(final InteractionUse iuse) {
        return (List<Gate>) iuse.getActualGates();
    }

    @SuppressWarnings("unchecked")
    public static List<Gate> getGates(final CombinedFragment cf) {
        return (List<Gate>) cf.getCfragmentGates();
    }

    public static GraphNode getGraphNode(Element element, GraphElement root) {
        return (GraphNode) getGraphElement(element, root);
    }

    public static GraphEdge getGraphEdge(Element element, GraphElement root) {
        return (GraphEdge) getGraphElement(element, root);
    }

    @SuppressWarnings("unchecked")
    public static GraphElement getGraphElement(Element element, GraphElement root) {
        List<GraphElement> list =
                LookForElement.lookForGraphElementWODiagram(element, root,
                        new ArrayList<GraphElement>(1));
        if (list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    /**
     * Determines whether the given name is a "default" name, i.e. a name of the form
     * "Prefix" + an integer.
     */
    public static boolean isDefaultName(final String name, final String prefix) {
        if (prefix == null)
            throw new IllegalArgumentException("prefix is null");

        if (name == null || !name.startsWith(prefix) || name.length() < prefix.length())
            return false;

        try {
            String suffix = name.substring(prefix.length());
            return Integer.parseInt(suffix) >= 1;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    @SuppressWarnings("unchecked")
    public static List<InteractionFragment> getCoveredBys(final Lifeline lifeline) {
        return (List<InteractionFragment>) lifeline.getCoveredBys();
    }

    /**
     * Determines whether the given InteractionFragment can only cover a single Lifeline.
     * 
     * @param fragment
     *            The InteractionFragment to test.
     * @return true if it can only cover one Lifeline. TODO consider: replace
     *         isSingleCovering with an attribute on GraphicalElement. this attribute can
     *         be set by createView synchronization handlers
     */
    public static boolean isSingleCovering(InteractionFragment fragment) {
        return fragment instanceof ExecutionOccurrenceSpecification
                || fragment instanceof InteractionConstraint
                || fragment instanceof StateInvariant
                || fragment instanceof DestructionEvent
                || (fragment instanceof CombinedFragment
                        && ((CombinedFragment) fragment).getInteractionOperator() == InteractionOperatorKind.PAR_LITERAL && fragment
                        .getCovereds().size() == 1);
    }

    @SuppressWarnings("unchecked")
    public static List<Lifeline> getLifelines(Interaction interaction) {
        return (List<Lifeline>) interaction.getLifelines();
    }

    @SuppressWarnings("unchecked")
    public static List<String> getBodies(final OpaqueExpression e) {
        return (List<String>) e.getBodies();
    }

    @SuppressWarnings("unchecked")
    public static List<Behavior> getOwnedBehaviors(final BehavioredClassifier bclass) {
        return (List<Behavior>) bclass.getOwnedBehaviors();
    }

    /**
     * Creates a unique name within a namespace. The name will begin with the given prefix
     * and end with an integer >= 1.
     */
    public static String createUniqueName(Namespace namespace, String prefix) {
        String name;
        int id = 1;
        do {
            name = prefix + id++;
        } while (hasOwnedMember(namespace, name));
        return name;
    }

    /**
     * Creates a name that is unique within the set of names. The name will begin wit the
     * given prefix and end with an integer >= 1.
     */
    public static String createUniqueName(final Collection<? extends Element> namespace,
            String prefix) {
        String name;
        int id = 1;
        do {
            name = prefix + id++;
        } while (containsName(namespace, name));
        return name;
    }

    private static boolean hasOwnedMember(Namespace namespace, String name) {
        // UML2 1.0.x
        for (Object member : namespace.getOwnedMembers()) {
            NamedElement namedMember = (NamedElement) member;
            if (name.equals(namedMember.getName()))
                return true;
        }
        return false;

        // TODO: UML2 1.1+
        // return namespace.getOwnedmember(name) != null;
    }

    private static boolean containsName(final Collection<? extends Element> namespace,
            final String name) {
        for (Element element : namespace) {
            if (element instanceof NamedElement) {
                NamedElement namedElement = (NamedElement) element;
                if (name.equals(namedElement.getName()))
                    return true;
            }
        }
        return false;
    }

    /**
     * Tests whether a given object is a view object references any model object at all.
     * Views that do not reference the model are typically either not correctly linked or
     * reference model objects that have been deleted.
     * 
     * @param o
     *            The instance to test.
     * @returns <code>true</code> if
     *          <ul>
     *          <li><code>o</code> is not null,
     *          <li><code>o</code> is a view object which does not reference any model
     *          object.
     *          </ul>
     *          Returns <code>false</code> otherwise.
     * @throws NullPointerException
     *             if <code>c</code> is null.
     */
    public static boolean neIsNullInstance(Object o) {
        if (o instanceof GraphElement) {
            Element element = ModelUtil.<Element> getSemantic((GraphElement) o);
            return (element instanceof NamedElement && ((NamedElement) element).getName() == null)
                    || (element == null);
        }
        return false;
    }

    /**
     * Tests whether a given object is a view object that references an Event of a
     * specific type.
     * 
     * @param o
     *            The instance to test.
     * @param c
     *            The desired model element class.
     * @returns <code>true</code> if
     *          <ul>
     *          <li><code>o</code> is not null,
     *          <li><code>o</code> is a view class and
     *          <li>the model element instance referenced by <code>o</code> is an
     *          instance of the class <code>c</code>.
     *          </ul>
     *          Returns <code>false</code> otherwise.
     * @throws NullPointerException
     *             if <code>c</code> is null.
     */
    public static boolean neEventIsInstance(Object o, Class<?> c) {
        if (!(o instanceof GraphElement))
            return false;

        GraphElement nev = (GraphElement) o;
        Element el = ModelUtil.<Element> getSemantic(nev);
        if (!(el instanceof NamedElement)) {
            return false;
        }
        NamedElement ne = (NamedElement) el;
        if (!(ne instanceof OccurrenceSpecification))
            return false;
        OccurrenceSpecification os = (OccurrenceSpecification) ne;
        return c.isInstance(os.getEvent());
    }

    /**
     * Tests whether a given object is a view object that references an Event that is
     * null.
     * 
     * @param o
     *            The instance to test.
     * @param c
     *            The desired model element class.
     * @returns <code>true</code> if
     *          <ul>
     *          <li><code>o</code> is not null,
     *          <li><code>o</code> is a view class and
     *          <li>the model element instance referenced by <code>o</code> is null.
     *          </ul>
     *          Returns <code>false</code> otherwise.
     * @throws NullPointerException
     *             if <code>c</code> is null.
     */
    public static boolean neEventIsNull(Object o) {
        if (!(o instanceof GraphElement))
            return false;

        GraphElement nev = (GraphElement) o;
        Element el = ModelUtil.<Element> getSemantic(nev);
        if (!(el instanceof NamedElement)) {
            return false;
        }
        NamedElement ne = (NamedElement) el;

        if (!(ne instanceof OccurrenceSpecification))
            return false;

        OccurrenceSpecification os = (OccurrenceSpecification) ne;
        return os.getEvent() == null;
    }

    @SuppressWarnings("unchecked")
    public static List<Comment> getOwnedComments(final Element e) {
        return (List<Comment>) e.getOwnedComments();
    }

    @SuppressWarnings("unchecked")
    public static List<PackageableElement> getPackagedElements(final org.eclipse.uml2.uml.Package e) {
        return (List<PackageableElement>) e.getPackagedElements();
    }

    @SuppressWarnings("unchecked")
    public static List<ValueSpecification> getArguments(final Message m) {
        return (List<ValueSpecification>) m.getArguments();
    }

    @SuppressWarnings("unchecked")
    public static List<Element> getOwnedElements(final Element e) {
        return (List<Element>) e.getOwnedElements();
    }

    @SuppressWarnings("unchecked")
    public static List<String> getLanguages(final OpaqueExpression e) {
        return (List<String>) e.getLanguages();
    }

    @SuppressWarnings("unchecked")
    public static List<Message> getMessages(final Interaction interaction) {
        return (List<Message>) interaction.getMessages();
    }

    @SuppressWarnings("unchecked")
    public static List<Classifier> getGenerals(final org.eclipse.uml2.uml.Classifier c) {
        return (List<Classifier>) c.getGenerals();
    }

    @SuppressWarnings("unchecked")
    public static List<Operation> getOwnedOperations(final org.eclipse.uml2.uml.Class c) {
        return (List<Operation>) c.getOwnedOperations();
    }

    @SuppressWarnings("unchecked")
    public static List<Operation> getOwnedOperations(final org.eclipse.uml2.uml.Interface i) {
        return (List<Operation>) i.getOwnedOperations();
    }

    @SuppressWarnings("unchecked")
    public static List<NamedElement> getOwnedMembers(final Namespace ns) {
        return (List<NamedElement>) ns.getOwnedMembers();
    }

    public static List<Interaction> getInteractions(Diagram diagram) {
        List<Interaction> ret = new LinkedList<Interaction>();
        for (GraphElement ge : getContents(diagram)) {
            if (getSemantic(ge) instanceof Interaction) {
                ret.add(ModelUtil.<Interaction> getSemantic(ge));
            }
        }
        return ret;
    }

    public static List<GraphElement> getContents(GraphElement element) {
        List<GraphElement> contents = new LinkedList<GraphElement>();
        TreeIterator<EObject> eObjects = element.eAllContents();
        while (eObjects.hasNext()) {
            EObject next = eObjects.next();
            if (next instanceof GraphElement && next != element) {
                contents.add((GraphElement) next);
            }
        }
        return contents;
    }

    private static Map<GraphEdge, String> sourceRefs = new WeakHashMap<GraphEdge, String>();
    private static Map<GraphEdge, String> targetRefs = new WeakHashMap<GraphEdge, String>();

    public static String getSourceRef(GraphEdge graphEdge) {
        return sourceRefs.get(graphEdge);
    }

    public static void setSourceRef(GraphEdge graphEdge, String ref) {
        sourceRefs.put(graphEdge, ref);
    }

    public static String getTargetRef(GraphEdge graphEdge) {
        return targetRefs.get(graphEdge);
    }

    public static void setTargetRef(GraphEdge graphEdge, String ref) {
        targetRefs.put(graphEdge, ref);
    }

    public static Namespace getBestNamespace(GraphElement graphElement) {
        GraphElement el = graphElement;
        Namespace namespace = null;
        while (!(el.getSemanticModel() instanceof Uml1SemanticModelBridge)) {
            el = el.getContainer();
        }
        Uml1SemanticModelBridge uml1SemanticModelBridge =
                (Uml1SemanticModelBridge) el.getSemanticModel();
        Element element = uml1SemanticModelBridge.getElement();
        while (!(element instanceof Namespace)) {
            element = element.getOwner();
        }
        namespace = (Namespace) element;
        return namespace;
    }
}
